今更Reactことはじめ
概要
React + MaterialUIで適当にウェッサイ作る。
SSRで動いてるのはすでに自社とかでもあったんだけど、全部自分で最初から描いてみよ、ってなるのは初。
UnityのWebGLで作ってもいいんだけど、せっかくだからやってみようと思う。
次こういう機会があったら、試しにAngular2を使ってみよう(微レ存
ちなみにパパっと使う上ではReact大満足。
StarterKit
Facebookが作ってるスターターキットがあったので使う。
https://facebook.github.io/react/docs/getting-started.html
こいつにはオフラインでもreactのフローが動くように幾つかのjsファイルが含まれている。
build/
react-dom-server.js
react-dom-server.min.js
react-dom.js
react-dom.min.js
react-with-addons.js
react-with-addons.min.js
react.js
react.min.js
これらだけで動かせる。
jsxのコンパイルだけは、下記watchを働かせるために、どこかから仕入れてきていた。
jsx --watch src/ build/
どっからもってきた(npm install ? )したのかよくわかってない。
GUI要素をJavaScriptで書く
書けるのな~~楽な~~~~~。
ReactDOMってのがグローバルに存在してるんで、htmlに書いてある適当な要素に対して、
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="build/react.js"></script>
<script src="build/react-dom.js"></script>
<script src="https://npmcdn.com/babel-core@5.8.38/browser.min.js"></script>
<script type="text/babel" src="src/helloworld.js"></script>
</head>
<body>
<div id="example"></div>
</body>
</html>
下記でGUI要素をJavaScriptからアタッチできる。
ReactDOM.render(
<CommentBox />,
document.getElementById('example')
);
で、アタッチする要素CommentBoxについては、これもjsから書ける。
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList />
<CommentForm />
</div>
);
}
});
render関数が描画される。
これらcreateClass関数で生成した要素は、コンポーネントという単位の雛形になる。
コンポーネント名としては、変数名があてがわれる。(上の場合だとCommentBoxっていうコンポーネントになる。
JSXで書いたrender関数の要素には、他のコンポーネントを入れ子にすることができる。
このコンポーネント間の参照の解釈タイミングは実際の実行時のため、コンポーネントを書いてる位置は問題にならない。
ただし、ReactDOM.render内で参照されるコンポーネントについては、必ず全ての参照がReactDOM.renderが呼ばれる前に解決されている必要がある。
つまりReactDOM.render関数は最後に書こうなという感じ。
入れ子になってる要素 CommentListとかCommentFormは、
同じようにcreateClass関数で作成できる。
var CommentForm = React.createClass({
render: function() {
return (
<div className="commentForm">
Hello, world! I am a CommentForm.
</div>
);
}
});
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
<Comment author="Pete Hunt">This is one comment</Comment>
<Comment author="Jordan Walke">This is *another* comment</Comment>
</div>
);
}
});
なんて楽なんだ、、、、
エラーの出方
例えば var Commentって書かれるべきクラスをCommmentとかtypoした場合、
CommentListから参照できない要素があるよっていうエラーが出る。
データを適応する
上記でベタが記してあるCommentListの中身を、jsonから読むようにする。
まずデータを用意して、
var data = [
{id: 1, author: "Pete Hunt", text: "This is one comment from json."},
{id: 2, author: "Jordan Walke", text: "This is *another* comment from json"}
];
次にデータを使うところまでの経路にデータ読み込む旨を書いていく。
ReactDOM.render(
<CommentBox data={data} />,
document.getElementById('example')
);
CommentBoxにデータを入れてるんで、CommentBoxにもデータをCommentListへと伝播するためのコードを書く。
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.props.data} />
<CommentForm />
</div>
);
}
});
CommentListにデータを渡したいので、data = {this.props.data} とか書く。
下流のパラメータ名 = {this.props.上流からのデータの名前} という記法で、データのマッピングがされるみたいだ。
ここでのthisはこれ、CommentBoxのインスタンスなのかな。
で、それを受けるCommentListは、
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(comment) {
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
この要素に渡ってくる、props内のdataという名前のパラメータに対して、JSXでDOM要素を返す関数を、組み込みのmap関数で適応する。
ようはCommentのリストを作り出している。らくちんかよ。
濃い要素の部分はJSXでCommentクラスの描画可能なパラメータを返してきてて、これがそのまま、
はるばるReactDOM.renderから渡ってきたjsonデータの中身の展開部分になっている。
Commentコンポーネントに対し、author、key、textをそれぞれ取り出して使用している。
author=author、key=id、で、このkey部分が特にコンポーネントで固定された意味を持っている。
っていうかkeyは自前定義ではなく、propsには初めから、virtualDOM用の要素としてkeyというパラメータが用意されている。
mapとかでリストを作り出す際に、uniqueを発揮するためにセットする必要がある。
差分更新のために、リストの要素にもunique性が必要な訳だ。
keyはそのuniqueをReact側が把握するために使用する。
で、最終的に{commentNodes}をreturnして終わり。
無事にビューへとデータが反映される。
すげーなーーこれ。
パラメータをサーバから持ってくる = APIを呼んでその値を入れるには?
いろんなパラメータ、状態、遷移を、なんかしっかりしたハンドラの仕組みによってものすごく折りたためる。
・取得元のurlを外部から与える形にできることによって、データ取得のソースの記述を外部化できる
・初期値をセットできる
・コンポーネントごとの挙動をそのurlパラメータに依存し、実行時処理の関数化ができる
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
上から順に、
loadCommentsFromServerっていう自前の関数定義をして、jQuery使ってデータを取得、成功したらdataに対してdataを反映。
getInitialStateっていう組み込みの関数定義をして、デフォルトのdata値をセット、
componentDidMountっていう組み込みの関数定義をして、このコンポーネントがマウントされた際、
loadCommentsFromServerを呼び出してdataに入力するjsonを取得、アタッチする。
render関数の中身にも変更があり、初期値としてセットした空のデータをまず読ませるために、this.state.dataを使っている。
ちなみにここでjqueryに依存してるけどサンプルがそうなってるだけなんでReact自体にjQuery依存はない。
(で、jQuery経由のsetStateでwarningが出るんですがこれは一体どうすればいいのか。)
新しいコメントの入力を行う機構を作りこむ
入力が行われたら、新しいCommentをサーバに送り込むことになる。
イベントの着火自体はCommentFormで行われるので、その入力を上位CommentBoxとかに届けたい。
変更 = コメントの追加があった際、親であるCommentBoxから、子であるCommentListに要素を加えることになるため、
親コンポーネントに何らかの方法でデータを伝達、処理させる必要がある。
ComentBoxでonCommentSubmitというプロパティを自作して、
下位のCommentFormに渡してCommentFormから入力時にCommentBoxの関数を実行できるようにする。
小さな範囲での密結合を生んでこれを解決している。
var CommentForm = React.createClass({
getInitialState: function() {
return {author: '', text: ''};
},
handleAuthorChange: function(e) {
this.setState({author: e.target.value});
},
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
// 入力をvalidate
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit(JSON.stringify({author: author, text: text}));
this.setState({author: '', text: ''});
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.author}
onChange={this.handleAuthorChange}
/>
<input
type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
);
}
});
nameとtextというテキストインプット2つをformで囲い、onSubmitにhandleSubmit関数をセットする。
明示的にthisが使えるのが嬉しい。
最終的にonSubmit関数内で、propsで渡ってくる関数onCommentSubmitを実行している。
onCommentSubmit関数は、CommentFormの親であるCommentBoxから渡している。
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'GET',
cache: false,
success: function(data) {
this.setState({data: data});// これがいけないぽい。
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
handleCommentSubmit: function(comment) {
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
onCommentSubmitというラベルで、CommentBoxのhandleCommentSubmit関数が接続されている。
最終的に渡ってきたcommentデータがpostされ、結果がsetStateを呼び出し、
変更が加えられたdataはそのままCommentListへと伝達される。
便利=~~
nodeとかサーバで動かすには
React routerとかが便利そう。
http://qiita.com/koba04/items/737c783f1189355e053f
ServerSideRendering
サーバ側でReact動かして完成したHTML(実際には動的な可変要素を含む)を返す技術。
htmlだけを返してローディングさせるのと対比されている。